package server.user;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;

import server.Logger;
import server.conn.ConnectionThread;
import server.conn.user.UserConnectionThread;
import server.db.DBObject;
import server.db.DataBase;
import server.order.Order;
import server.order.OrderController;
import server.price.PriceController;

public class User extends DBObject {
	public static final String TABLE = "users";
	public static final String ID = "id";
	public static final String USERNAME = "username";
	public static final String PASSWORD = "passwd";
	public static final String REAL_NAME = "name";
	public static final String EMAIL = "email";
	public static final String LEVERAGE = "leverage";
	public static final String ACTIVE = "active";
	public static final String ADMIN = "admin";
	
	private int id;
	private String userName;
	private String password;
	private String realName;
	private String eMail;
	private float leverage;
	private boolean active;
	private boolean admin;
	
	private HashMap<String, Account> accounts;
	
	/**
	 * Create a user by its ID
	 * @param id	User ID
	 * @throws SQLException
	 */
	public User(int id) throws SQLException {
		super();
		this.id = id;
		
		getInfoFromID();
		initAccounts();
	}
	
	/**
	 * Create a user by its userName and password.
	 * Throws SQLException if user cannot be found or there is a database problem
	 * Throws BadUserException if the user is not active or the password is wrong
	 * @param userName	User's userName
	 * @param password	User's password
	 * @throws SQLException
	 * @throws BadUserException
	 */
	public User(String userName, String password) throws SQLException, BadUserException {
		super();
		this.userName = userName;
		this.password = password;
		
		if (checkPassword()) {
			getInfoFromName();
			if (!active)
				throw new BadUserException("Account is not active!");
			initAccounts();
		} else {
			throw new BadUserException("Bad username or password!");
		}	
	}
	
	/**
	 * Fill in user info if it was created from an ID
	 * @throws SQLException
	 */
	private final void getInfoFromID() throws SQLException {
		String query = "SELECT * FROM "+User.TABLE+" WHERE id="+id+" LIMIT 1";
		setFromRS(doQuery(query));
	}
	
	/**
	 * Fill in the user info if it was created from a userName
	 * @throws SQLException
	 */
	private final void getInfoFromName() throws SQLException {
		String query = "SELECT * FROM "+User.TABLE+" WHERE "+User.USERNAME+"=\""+userName+"\" LIMIT 1";
		setFromRS(doQuery(query));
	}
	
	/**
	 * Set user attributes from a ResultSet
	 * @param rs	ResultSet containing user row
	 * @throws SQLException
	 */
	private final void setFromRS(ResultSet rs) throws SQLException  {
			rs.first();
			id = rs.getInt("id");
			userName = rs.getString(User.USERNAME);
			password = rs.getString(User.PASSWORD);
			realName = rs.getString(User.REAL_NAME);
			eMail = rs.getString(User.EMAIL);
			leverage = rs.getFloat(User.LEVERAGE);
			admin = rs.getBoolean(User.ADMIN);
			active = rs.getBoolean(User.ACTIVE);
	}
	
	/**
	 * Check to see if the user password is correct
	 * @return True if the password was correct
	 */
	public final boolean checkPassword() {
		String query = "SELECT "+User.PASSWORD+" FROM "+User.TABLE+" WHERE "+User.USERNAME+"=\""+userName+"\" LIMIT 1";
		ResultSet rs;
		try {
			rs = doQuery(query);
			rs.first();
			return this.password.equals(rs.getString(User.PASSWORD));
		} catch (SQLException e) {
			Logger.getInstance().addError("Could not find user", 2);
			return false;
		}
	}
	
	public int getID() { return id; }
	public String getUserName() { return userName; }
	public String getRealName() { return realName; }
	public String getEMail() { return eMail; }
	public float getLeverage() { return leverage; }
	public boolean isActive() { return active; }
	public boolean isAdmin(){ return admin; }
	
	// -- ACCOUNT SECTION --------
	
	/**
	 * Add an Account for each of ACE's currencies
	 */
	private void initAccounts() throws SQLException {
		accounts = new HashMap<String, Account>();
		
		String[] currs = PriceController.getInstance().getCurrencies();
		
		for (String curr : currs) {
			accounts.put(curr, new Account(curr,this));
		}
	}
	
	/**
	 * Return the specified account
	 * @param symbol	Symbol of the requested account
	 * @return	The account if it exists else null
	 */
	public Account getAccount(String symbol) {	
		return (accounts.containsKey(symbol)) ? accounts.get(symbol) : null;
	}
	
	/**
	 * Return an array of string representation of each of the users accounts
	 * @return	Array of string representing accounts
	 */
	public String[] accountsToString() {
		String[] retArray = new String[PriceController.getInstance().getCurrencies().length];
		
		int counter = 0;
		for (String curr : PriceController.getInstance().getCurrencies()) {
			retArray[counter++] = accounts.get(curr).toString();
		}
		
		return retArray;
	}
	
	/**
	 * Return the string representation of one account
	 * @param sym	Account symbol
	 * @return	String representation of account
	 */
	public String accountToString(String sym) {
		if (accounts.containsKey(sym)) {
			return accounts.get(sym).toString();
		} else {
			return UserConnectionThread.ERROR+UserConnectionThread.SPLIT+"Account doesn't exist";
		}
	}
	
	/**
	 * 
	 * @param sym	Symbol of requested account
	 * @param limit	Maximum number of records to return
	 * @return	Array of strings showing accounts history
	 * @throws SQLException
	 */
	public String[] accountHistoryString(String sym, int limit) throws SQLException {		
		if (accounts.containsKey(sym)) {
			return accounts.get(sym).getHistoryString(limit);
		} else {
			String[] retArray = new String[1];
			retArray[0] = UserConnectionThread.ERROR+UserConnectionThread.SPLIT+"No such account!";
			return retArray;
		}
	}
	
	/**
	 * String representation of user
	 */
	public String toString(){
		String ret;
		ret = ""+id;
		ret+= ConnectionThread.SPLIT+userName;
		ret+= ConnectionThread.SPLIT+realName;
		ret+= ConnectionThread.SPLIT+eMail;
		ret+= ConnectionThread.SPLIT+leverage;
		ret+= ConnectionThread.SPLIT+active;
		ret+= ConnectionThread.SPLIT+admin;
		return ret;
	}
	
	/**
	 * Freeze or unfreeze user
	 * @param unfreeze	True to freeze account, false to unfreeze
	 * @return	The success of requested operation
	 */
	public boolean freeze(boolean unfreeze){
		int value = unfreeze ? 0 : 1;
		String query = "UPDATE "+User.TABLE+" SET "+User.ACTIVE+"="+value+" WHERE "+User.ID+"="+id+"";
		try {
			doUpdate(query);
			Logger.getInstance().addLog("User (id = "+id+") has been "+(unfreeze ? "un":"")+"frozen", 3);
			return true;
		} catch (SQLException e) {
			Logger.getInstance().addSQLQueryError("Could not "+(unfreeze ? "un":"")+"freeze user "+id, query, 2);
			return false;
		}
	}
	
	/**
	 * Set the user info and update the database
	 * @param username	New userName
	 * @param name		New real name
	 * @param email		New email
	 * @param leverage	New leverage
	 * @return			Success of the update
	 */
	public boolean setInfo(String username, String name, String email, float leverage){
		String query = "UPDATE "+User.TABLE+" SET "+User.USERNAME+"='"+username+"', "+User.REAL_NAME+"='"+name+"', "+User.EMAIL+"='"+email+"', "+User.LEVERAGE+"='"+leverage+"' WHERE "+User.ID+"='"+id+"'";
		try {
			doUpdate(query);
			Logger.getInstance().addLog("Changed info for user \""+userName+"\", id = "+id, 4);
			return true;
		} catch (SQLException e) {
			Logger.getInstance().addSQLQueryError("Could not edit information for user \""+userName+"\", id = "+id, query, 2);
			return false;
		}
	}
	
	/**
	 * Change the users password
	 * @param newpass	New password
	 * @return			The success of the update
	 */
	public boolean setPass(String newpass){
		String query = "UPDATE "+User.TABLE+" SET "+User.PASSWORD+"=\""+newpass+"\" WHERE "+User.ID+"=\""+id+"\"";
		try {
			doUpdate(query);
			Logger.getInstance().addLog("Changed password for user \""+userName+"\", id = "+id, 4);
			return true;
		} catch (SQLException e) {
			Logger.getInstance().addSQLQueryError("Could not set password for user \""+userName+"\", id = "+id, query, 2);
			return false;
		}
	}
	// -- ORDER SECTION --------
	
	/**
	 * Return an array of open orders in string format
	 * @return	Array of strings showing open orders
	 * @throws SQLException
	 */
	public String[] getOpenOrders() throws SQLException {
		String query = "SELECT * FROM "+Order.TABLE+" WHERE ("+Order.USER_ID+"="+id+" AND "+Order.CLOSE_TIME+"=0) ORDER BY "+Order.OPEN_TIME+" ASC";
		ResultSet rs = doQuery(query);
		
		String[] ret = new String[DataBase.getRows(rs)];
		int counter = 0;
		do {
			ret[counter++] = Order.getOrderFromRS(rs).toString();
		} while (rs.next());
		
		return ret;
	}
	
	/**
	 * Return the users closed orders
	 * @param limit	Maximum number of orders to return
	 * @return		String array of closed orders
	 * @throws SQLException
	 */
	public String[] getClosedOrders(int limit) throws SQLException {
		if (limit > 25)
			limit = 25;
		String query = "SELECT * FROM "+Order.TABLE+" WHERE ("+Order.USER_ID+"="+id+" AND "+Order.CLOSE_TIME+"!=0 AND ";
		query += Order.ID+"!="+Order.SPLIT+") ORDER BY "+Order.CLOSE_TIME+" DESC LIMIT "+limit;
		
		ResultSet rs = doQuery(query);
		
		String[] ret = new String[DataBase.getRows(rs)];
		int counter = 0;
		do {
			ret[counter++] = Order.getOrderFromRS(rs).toString();
		} while (rs.next());
		
		return ret;
	}
	
	/**
	 * Open a new order
	 * @param newOrder	The order to be opened
	 * @throws InvalidOrderException
	 * @throws SQLException
	 */
	public void openOrder(Order newOrder) throws InvalidOrderException, SQLException {
		if (newOrder.getBType() == Order.SELL)
			newOrder.switchCurs();
		
		if (accounts.get(newOrder.getCur2()).checkOrder(newOrder)) {
			newOrder.openOrder();
			OrderController.getInstance().addOrder(newOrder);
		} else {
			throw new InvalidOrderException("You do not have enough funds");
		}
	}
}